package wsi.detect;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Logger;

import org.json.JSONObject;
import org.json.XML;
import org.rygh.core.util.UIo;
import org.rygh.semantic.gui.swing.input.Input;

import wsi.dataguide.tree.WSXmlParser;
import wsi.db.WebCache;
import wsi.function.DirManager;
import wsi.function.Function;
import wsi.function.Function.Phrase;
import wsi.function.Url;
import wsi.function.oauth.OAuthController;
import wsi.match.SubtreeMatch;
import wsi.script.AppConfig;

public class Caller {
	public static class CallResult {
		public String content;
		public boolean fromCache;

		public CallResult(String content, boolean fromCache) {
			super();
			this.content = content;
			this.fromCache = fromCache;
		}

		boolean isEmpty() {
			return content == null || content.trim().isEmpty();
		}
	}

	public static File getXmlFile(Input input, File dir) {
		String fname = String.format("%s.xml", input.toFilename());

		return new File(dir, fname);
	}

	private final Logger log = Logger.getLogger(getClass().getName());

	private final DetectHelper detector = new DetectHelper();
	private final DirManager dir;
	private final Function function;

	private boolean cacheWhatever = false;
	private boolean saveInputFile = true;
	private OAuthController oauthCtrl;

	public Caller(DirManager dir) {
		this.dir = dir;
		function = dir.getFunction();
	}

	public Caller(Function function) {
		this(new DirManager(function));
	}

	/**
	 * <s>if we cannot find match, then delete this html in cache.</s>
	 * 
	 * @param dir2
	 */
	private void cache(Input input, String content) {
		File file = getXmlFile(input);
		String suri = getUri(input.getValue());

		String xml = postprocess(content);
		boolean needToCache = false;

		if (input.getPredicate() == null) {
			// in this case, save without match check.

			String res = WSXmlParser.checkValid(input, xml);

			needToCache = res != null;
		} else {
			// otherwise, save by checking match.

			// will probe and extract subgraph here

			SubtreeMatch sm = detector.detect(input, function, xml);

			needToCache = sm != null && sm.size() > 0;
		}

		if (needToCache) {

			// save original content to cache, json. do not convert.
			WebCache.cache(suri, content);
		} else {
			file.delete();
			WebCache.delete(suri);

			System.out.println("cache delete: " + suri);
		}
	}

	/**
	 * do not transform json to xml here. make it pure.
	 * 
	 * @param value
	 * @return
	 */
	public String call(Input input) {
		if (!input.isValid())
			return null;

		String val = input.getValue();
		File file = getXmlFile(input);

		if (saveInputFile)
			if (file.exists()) {
				// System.out.printf("HIT FILE %s\n", file.getAbsolutePath());
				return UIo.readFile(file);
			}

		//

		String suri = getUri(val);

		if (suri == null)
			return null;

		CallResult result = null;

		if (oauthCtrl == null)
			result = readUrl(suri);
		else if (function.getMethod() != null) {
			switch (function.getMethod()) {
			case "get":
				result = oauthCtrl.get(suri);
				break;

			case "post":
				Url url = function.getUrl();
				String convert = function.getConvert();

				result = oauthCtrl.post(url.toString(),
						function.getParams(0, val, convert));
				break;

			default:
				assert false;
				break;
			}
		}

		if (cacheWhatever) {
			String s = result.content == null ? "" : result.content;
			WebCache.cache(suri, s, true);
		}

		if (result.isEmpty()) {
			// log.info("[warn] empty xml: " + suri);
			return null;
		}

		if (saveInputFile)
			UIo.saveFile(file, result.content);

		if (!result.fromCache) {
			// System.out.println("  begin to cache " + input);

			cache(input, result.content);

			// System.out.println("  end caching " + input);
		}

		return result.content;
	}

	/**
	 * call with the body string (without prefix and suffix), save with the
	 * whole value.
	 * 
	 * @param inputs
	 */
	public void call(Input[] inputs) {
		if (inputs == null || !prepare())
			return;

		for (int i = 0; i < inputs.length; i++) {
			Input input = inputs[i];

			// log.info(String.format("processing input %d: %s", (i + 1),
			// value));

			call(input);

			if (function.isSkipped(Phrase.REST)) {
				log.info("break for trial.");
				break;
			}
		}
	}

	private String clear(final String value) {
		int p1 = -1, p2 = value.length();
		char c;

		do {
			p1++;

			if (p1 == value.length())
				break;

			c = value.charAt(p1);
		} while (!Character.isLetterOrDigit(c) && c != '(');

		do {
			p2--;

			if (p2 < 0)
				break;

			c = value.charAt(p2);
		} while (!Character.isLetterOrDigit(c) && c != ')');

		String val;

		if (p1 < 0 || p2 + 1 > value.length() || p2 + 1 - p1 < 0) {
			System.out.printf("[error] cannot clear %s\n", value);
			return value;
		} else
			val = value.substring(p1, p2 + 1);

		val = val.replace('/', '_');

		/*
		 * do not replace (), mymovieapi needs this. Titanic (1997)
		 */
		// value = value.replace('(', '_');
		// value = value.replace(')', '_');

		return val;
	}

	public String getUri(String value) {
		String val = clear(value);

		Url url = function.getUrl();
		String convert = function.getConvert();
		String surl;

		surl = url.reify(0, val, convert);

		try {
			URI uri = new URI(surl);
			surl = uri.toASCIIString();
		} catch (URISyntaxException e) {
			System.out.printf("[warn] cannot get uri from %s\n", surl);
			return null;
		}

		return surl;
	}

	private File getXmlFile(Input input) {
		return getXmlFile(input, dir.getXmlDir());
	}

	public String postprocess(String value) {
		if (value == null || value.isEmpty())
			return value;

		String format = function.getFormat();

		if (format != null && format.equals("json")) {
			try {
				JSONObject obj = new JSONObject(value);
				value = XML.toString(obj, "response");
			} catch (Exception e) {
				e.printStackTrace();
				log.info("bad json: " + value);
				return null;
			}
		}

		return value;
	}

	private boolean prepare() {
		String oauth = function.getOauth();

		if (oauth != null) {
			oauthCtrl = new OAuthController(oauth);

			/*
			 * for debug
			 */
			// oauthCtrl.setCache(false);

			if (!oauthCtrl.connect(function)) {
				log.info("oauth connection failed.");
				oauthCtrl = null;
				return false;
			}
		}

		return true;
	}

	private CallResult readUrl(String suri) {
		String content = null;

		boolean cache = AppConfig.shared().isWebCacheOn();

		// if (function.getName().equals("getReleaseById")) {
		// cache = false;
		// }

		if (cache) {
			content = WebCache.fetch(suri);

			if (content != null) {
				// System.out.println("HIT URL " + suri);
				// System.out.println("HIT: " + WebCache.getHashCode(suri));

				return new CallResult(content, true);
			}
		}

		content = UIo.readUrl(suri);

		// if (function.getName().equals("getReleaseById")) {
		// System.out.printf("call web %s: %s\n", suri, content);
		// }

		return new CallResult(content, false);
	}

	public void setCacheWhatever(boolean cacheWhatever) {
		this.cacheWhatever = cacheWhatever;
	}

	public void setSaveInputFile(boolean saveInputFile) {
		this.saveInputFile = saveInputFile;
	}
}
